#include "qabstractdrive.h"

#include "qproject.h"
#include "qdriveattribute.h"
#include "qdrivevaluemap.h"
#include "qdrivemission.h"
#include "qdatamanager.h"

#include <QFile>
#include <QDir>
#include <QXmlStreamReader>
#include <QXmlStreamWriter>

QAbstractDrive::QAbstractDrive(QObject *parent) :
    QObject(parent),
    m_valueMap(new QDriveValueMap),
    m_device(NULL)
{
    initAttribute();
    connect(m_valueMap,SIGNAL(changed()),this,SIGNAL(valueChanged()));
    m_timer=new QTimer(this);
    connect(m_timer,SIGNAL(timeout()),this,SLOT(run()));
}

QAbstractDrive::~QAbstractDrive()
{
    qDeleteAll(m_attributes);
    m_attributes.clear();
}

bool QAbstractDrive::addDriveMission(QDriveMission *mission)
{
    if(mission->dataLengh()<=0
    || mission->valueAddress()<0
    || mission->valueAddress()>=65536
    || mission->period()<1)
    {
        return false;
    }
    foreach(QDriveMission *ms,m_missionList)
    {
        if(ms->missionID()==mission->missionID()
        && ms->deviceAddress()==mission->deviceAddress())
        {
            return false;
        }
    }
    mission->setLastTime(QDateTime::currentDateTime());
    mission->setMissionState(MS_NOTDO);
    m_missionList.append(mission);
    return true;
}

void QAbstractDrive::initAttribute()
{
    QDriveAttribute *attr;

    attr=new QDriveAttribute("Name");
    attr->setAttribute("isEdited",false);

    appendAttribute(attr);

    attr=new QDriveEnumAttribute("TimeOut(ms)");
    attr->setValue("1000");
    attr->setAttribute("Enum",QStringList()<<"200"<<"500"<<"1000"<<"2000"<<"3000"<<"4000"<<"5000");
    attr->setAttribute("isEdited",true);
    appendAttribute(attr);

    attr=new QDriveEnumAttribute("Retry");
    attr->setValue("3");
    attr->setAttribute("Enum",QStringList()<<"1"<<"2"<<"3"<<"4"<<"5"<<"6"<<"7");
    attr->setAttribute("isEdited",true);

    appendAttribute(attr);

    QHeaderInfo *info;

    info=new QHeaderInfo("Value Address");
    info->setProperty("type","int");
    info->setProperty("Min","0");
    info->setProperty("Max","65535");
    info->setProperty("default_value","0");
    m_valueMap->addHeader(info);

    info=new QHeaderInfo("Period(s)");
    info->setProperty("type","int");
    info->setProperty("Min","1");
    info->setProperty("Max","3600");
    info->setProperty("default_value","5");
    m_valueMap->addHeader(info);

    info=new QHeaderInfo("Scale");
    info->setProperty("type","ComboBox");
    info->setProperty("default_value","1");
    info->setProperty("Enum",QStringList()<<"1"<<"10"<<"100"<<"1000"<<"10000");
    m_valueMap->addHeader(info);

    info=new QHeaderInfo("Sign");
    info->setProperty("type","ComboBox");
    info->setProperty("default_value","False");
    info->setProperty("Enum",QStringList()<<"True"<<"False");
    m_valueMap->addHeader(info);

    info=new QHeaderInfo("Memery");
    info->setProperty("type","Memery_Enum");
    m_valueMap->addHeader(info);

    info=new QHeaderInfo("Level");
    info->setProperty("type","int");
    info->setProperty("Min","1");
    info->setProperty("Max","10");
    info->setProperty("default_value","5");
    m_valueMap->addHeader(info);

    info=new QHeaderInfo("Type");
    info->setProperty("type","ComboBox");
    info->setProperty("Enum",QStringList()<<"Cycle"<<"Temporary");
    info->setProperty("default_value","Cycle");
    m_valueMap->addHeader(info);

    info=new QHeaderInfo("Information");
    info->setProperty("type","LineEdit");
    info->setProperty("default_value","");
    m_valueMap->addHeader(info);
}

QDriveValueMap* QAbstractDrive::driveValueMap()
{
    return m_valueMap;
}

void QAbstractDrive::saveValue()
{
    QString str=QProject::project()->getProjectPath()+"/drives";
    QDir dir(str);
    if(!dir.exists())
    {
        dir.mkpath(str);
    }
    QFile f(str+"/"+attribute("Name")->value().toString()+"_value.xml");
    if(!f.open(QFile::ReadWrite))
    {
        return;
    }

    QXmlStreamWriter w(&f);

    w.writeStartDocument();
    w.setAutoFormatting(true);

    w.writeStartElement("Drive_Value");

    int column=m_valueMap->columnCount();
    int row=m_valueMap->rowCount();

    for(int i=0;i<row;i++)
    {
        w.writeStartElement("Row");
        for(int j=0;j<column;j++)
        {
            w.writeStartElement("Column");
            w.writeCharacters(m_valueMap->value(i,j));
            w.writeEndElement();
        }

        w.writeEndElement();
    }


    w.writeEndElement();


    w.writeEndDocument();
}

void QAbstractDrive::readValue()
{
    QString str=QProject::project()->getProjectPath()+"/drives";
    QDir dir(str);
    if(!dir.exists())
    {
        dir.mkpath(str);
    }
    QFile f(str+"/"+attribute("Name")->value().toString()+"_value.xml");
    if(!f.open(QFile::ReadWrite))
    {
        return;
    }

    QXmlStreamReader r(&f);

    while(!r.atEnd())
    {
        if(r.readNext()==QXmlStreamReader::StartElement)
        {
            if(r.name()=="Drive_Value")
            {
                readValueRow(&r);
            }
        }
    }
}

void QAbstractDrive::readValueRow(QXmlStreamReader *r)
{
    for(bool finished=false;!finished && !r->hasError();)
    {
        switch(r->readNext())
        {
        case QXmlStreamReader::StartElement:
            if(r->name()=="Row")
            {
                m_valueMap->addRow();
                readValueColumn(r,m_valueMap->rowCount()-1);
            }
            break;
        case QXmlStreamReader::EndElement:
            finished=true;
            break;
        default:
            break;
        }
    }
}

void QAbstractDrive::readValueColumn(QXmlStreamReader *r, int row)
{
    int column=0;
    for(bool finished=false;!finished && !r->hasError();)
    {
        switch(r->readNext())
        {
        case QXmlStreamReader::StartElement:
            if(r->name()=="Column")
            {
                m_valueMap->setValue(row,column,readCell(r));
                column++;
            }
            break;
        case QXmlStreamReader::EndElement:
            finished=true;
            break;
        default:
            break;
        }
    }
}

QString QAbstractDrive::readCell(QXmlStreamReader *r)
{
    QString ret;
    for(bool finished=false;!finished && !r->hasError();)
    {
        switch(r->readNext())
        {
        case QXmlStreamReader::StartElement:
            break;
        case QXmlStreamReader::EndElement:
            finished=true;
            break;
        case QXmlStreamReader::Characters:
            ret=r->text().toString();
            break;
        default:
            break;
        }
    }
    return ret;
}

void QAbstractDrive::saveConfig()
{
    QString str=QProject::project()->getProjectPath()+"/drives";
    QDir dir(str);
    if(!dir.exists())
    {
        dir.mkpath(str);
    }
    QFile f(str+"/"+attribute("Name")->value().toString()+"_config.xml");
    if(!f.open(QFile::ReadWrite))
    {
        return;
    }

    QXmlStreamWriter w(&f);

    w.writeStartDocument();
    w.setAutoFormatting(true);

    w.writeStartElement("Drive_Config");
    foreach(QDriveAttribute* attr,m_attributes)
    {
        w.writeStartElement("Attribute");
        w.writeAttribute("name",attr->name());
        attr->write(w);
        w.writeEndElement();
    }
    w.writeEndElement();
    w.writeEndDocument();

    f.close();
}

void QAbstractDrive::readConfig()
{
    QString str=QProject::project()->getProjectPath()+"/drives";
    QDir dir(str);
    if(!dir.exists())
    {
        dir.mkpath(str);
    }
    QFile f(str+"/"+attribute("Name")->value().toString()+"_config.xml");
    if(!f.open(QFile::ReadWrite))
    {
        return;
    }

    QXmlStreamReader r(&f);

    while(!r.atEnd())
    {
        if(r.readNext()==QXmlStreamReader::StartElement)
        {
            if(r.name()=="Drive_Config")
            {
                readAttributes(&r);
            }
        }
    }
}

void QAbstractDrive::readAttributes(QXmlStreamReader *r)
{
    for(bool finished=false;!finished && !r->hasError();)
    {
        switch(r->readNext())
        {
        case QXmlStreamReader::StartElement:
            if(r->name()=="Attribute")
            {
                readAttribute(r);
            }
            break;
        case QXmlStreamReader::EndElement:
            finished=true;
            break;
        default:
            break;
        }
    }
}

void QAbstractDrive::readAttribute(QXmlStreamReader *r)
{
    QDriveAttribute *attr;
    foreach(const QXmlStreamAttribute &a,r->attributes())
    {
        if(a.name().toString()=="name")
        {
            attr=attribute(a.value().toString());
        }
    }
    if(attr!=NULL)
    {

        attr->read(*r);
    }
    else
    {
        for(bool finished=false;!finished && !r->hasError();)
        {
            switch(r->readNext())
            {
            case QXmlStreamReader::StartElement:
                break;
            case QXmlStreamReader::EndElement:
                finished=true;
                break;
            default:
                break;
            }
        }
    }
}

QList<QDriveAttribute*> QAbstractDrive::attributes()
{
    return m_attributes;
}

QDriveAttribute* QAbstractDrive::attribute(const QString &name)
{
    foreach(QDriveAttribute* attr,m_attributes)
    {
        if(attr->name()==name)
        {
            return attr;
        }
    }
    return NULL;
}

void QAbstractDrive::appendAttribute(QDriveAttribute *attr)
{
    m_attributes.removeAll(attr);
    m_attributes.append(attr);
    connect(attr,SIGNAL(valueChanged()),this,SIGNAL(attributeChanged()));
}

void QAbstractDrive::start()
{
    initMission();
    initConnect();
    connect(m_device,SIGNAL(readyRead()),this,SLOT(read()));
}

void QAbstractDrive::initMission()
{
}

void QAbstractDrive::stop()
{
    m_timer->stop();
    m_device->close();
    qDeleteAll(m_missionList);
    m_missionList.clear();
    m_currentMission=NULL;
    //releaseConnect();
}

void QAbstractDrive::read()
{
    QByteArray data=m_device->readAll();
    analyseData(data);
}

void QAbstractDrive::analyseData(const QByteArray &data)
{

}

void QAbstractDrive::initConnect()
{

}

void QAbstractDrive::releaseConnect()
{

}

void QAbstractDrive::stateChanged(int state)
{
    if(state==3)
    {
        m_currentMission=NULL;
        m_timer->start(10);
    }
    else if(state==0 || state==6)
    {
        stop();
    }
}

void QAbstractDrive::run()
{
    if(m_currentMission!=NULL)
    {
        return;
    }
    QDriveMission *ms=needTodo();
    if(ms!=NULL)
    {
        sendMission(ms);
    }
}

QDriveMission* QAbstractDrive::needTodo()
{
    QDateTime mt=QDateTime::currentDateTime();
    QDriveMission *ms=NULL;

    foreach(QDriveMission *temp,m_missionList)
    {
        if(temp->type()==MS_NOTDO && (temp->type()==MT_TEMP || (temp->lastTime().msecsTo(mt)/1000)>=temp->period()))
        {
            if(ms==NULL || temp->level()<ms->level())
            {
                ms=temp;
            }
        }
    }

    return ms;
}

void QAbstractDrive::sendMission(QDriveMission *ms)
{
    ms->setMissionState(MS_DOING);
    QByteArray array=getSendData(ms);
    if(array.size()>0)
    {
        m_device->write(array);
        m_currentMission=ms;
    }
    else
    {
        missionDone(ms);
    }
}

QByteArray QAbstractDrive::getSendData(QDriveMission *ms)
{
    return QByteArray();
}

void QAbstractDrive::missionDone(QDriveMission *ms)
{
    if(ms->type()==MT_TEMP)
    {
        ms->setMissionState(MS_DELETE);
    }
    else
    {
        ms->setMissionState(MS_NOTDO);
    }
    ms->setLastTime(QDateTime::currentDateTime());

    int c=m_valueMap->column("Memery");
    int v=m_valueMap->column("Value Address");
    int s=m_valueMap->column("Scale");
    int sig=m_valueMap->column("Sign");
    int count=m_valueMap->rowCount();

    ushort *us=ms->data();
    int addr=ms->valueAddress();
    int len=ms->dataLengh();

    for(int i=0;i<count;i++)
    {
        int ad=m_valueMap->value(i,v).toInt();
        if(ad>=addr && ad<addr+len)
        {
            QString value=m_valueMap->value(i,c);
            ushort data=us[ad-addr];
            int scale=m_valueMap->value(i,s).toInt();
            double dat;
            if(m_valueMap->value(i,sig)=="True")
            {
                if(data&0x8000)
                {
                    dat=data-0xffff-1;
                }
                else
                {
                    dat=data;
                }

            }
            else
            {
                dat=data;
            }
            if(scale!=0)
            {
                dat=(double)dat/scale;
            }
            QDataManager::manager()->setUserData(value,dat);
        }
    }

    m_currentMission=NULL;
}

int QAbstractDrive::CRC_Value(const QByteArray array, int iLen)
{
    if(array.size()<iLen || iLen==0)
    {
        return 0;
    }
    unsigned char *pBuf;
    pBuf=new unsigned char[iLen];
    memcpy(pBuf,array.data(),iLen);
    int i;
    int crc_value=0xffff;
    while(iLen--)
    {
        crc_value^=*pBuf++;
        for(i=0;i<8;i++)
        {
            if(crc_value&0x0001)
            {
                crc_value=(crc_value>>1)^0xa001;
            }
            else
            {
                crc_value=crc_value>>1;
            }
        }
    }
    int temp;
    temp=((crc_value)>>8)&0xff;
    temp+=((crc_value<<8)&0xff00);
    crc_value=temp;
    delete [] pBuf;
    return crc_value;
}
